package com.xxchat.service.impl;

import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import com.xxchat.config.AppConfig;
import com.xxchat.entity.constants.RedisConstants;
import com.xxchat.entity.constants.SysConstants;
import com.xxchat.entity.constants.UserInfoConstants;
import com.xxchat.entity.dto.GeoInfoDTO;
import com.xxchat.entity.dto.MessageSendDTO;
import com.xxchat.entity.dto.TokenUserInfoDTO;
import com.xxchat.entity.enums.*;
import com.xxchat.entity.po.ChatSessionUser;
import com.xxchat.entity.po.UserContact;
import com.xxchat.entity.po.UserInfoBeauty;
import com.xxchat.entity.query.*;
import com.xxchat.entity.vo.UserInfoVO;
import com.xxchat.exception.BusinessException;
import com.xxchat.mappers.ChatSessionUserMapper;
import com.xxchat.mappers.UserContactMapper;
import com.xxchat.mappers.UserInfoBeautyMapper;
import com.xxchat.redis.RedisComponent;
import com.xxchat.redis.RedisUtils;
import com.xxchat.service.UserContactService;
import com.xxchat.utils.CopyUtils;
import com.xxchat.utils.IPAreaUtils;
import com.xxchat.websocket.MessageHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.stereotype.Service;

import com.xxchat.entity.po.UserInfo;
import com.xxchat.entity.vo.PaginationResultVO;
import com.xxchat.mappers.UserInfoMapper;
import com.xxchat.service.UserInfoService;
import com.xxchat.utils.StringTools;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;


/**
 * 用户信息表 业务接口实现
 *
 * @author Waicc
 */
@Slf4j
@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {

    @Resource
    private UserInfoMapper<UserInfo, UserInfoQuery> userInfoMapper;

    @Resource
    private UserInfoBeautyMapper<UserInfoBeauty, UserInfoBeautyQuery> userInfoBeautyMapper;

    @Resource
    private UserContactMapper<UserContact, UserContactQuery> userContactMapper;

    @Resource
    private ChatSessionUserMapper<ChatSessionUser, ChatSessionUserQuery> chatSessionUserMapper;

    @Resource
    private UserContactService userContactService;

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private RedisComponent redisComponent;

    @Resource
    private AppConfig appConfig;

    @Resource
    private MessageHandler messageHandler;

    /**
     * 根据条件查询列表
     */
    @Override
    public List<UserInfo> findListByParam(UserInfoQuery param) {
        return this.userInfoMapper.selectList(param);
    }

    /**
     * 根据条件查询列表
     */
    @Override
    public Integer findCountByParam(UserInfoQuery param) {
        return this.userInfoMapper.selectCount(param);
    }

    /**
     * 分页查询方法
     */
    @Override
    public PaginationResultVO<UserInfo> findListByPage(UserInfoQuery param) {
        int count = this.findCountByParam(param);
        int pageSize = param.getPageSize() == null ? PageSize.SIZE15.getSize() : param.getPageSize();

        SimplePage page = new SimplePage(param.getPageNo(), count, pageSize);
        param.setSimplePage(page);
        List<UserInfo> list = this.findListByParam(param);
        PaginationResultVO<UserInfo> result = new PaginationResultVO(count, page.getPageSize(), page.getPageNo(), page.getPageTotal(), list);
        return result;
    }

    /**
     * 新增
     */
    @Override
    public Integer add(UserInfo bean) {
        return this.userInfoMapper.insert(bean);
    }

    /**
     * 批量新增
     */
    @Override
    public Integer addBatch(List<UserInfo> listBean) {
        if (listBean == null || listBean.isEmpty()) {
            return 0;
        }
        return this.userInfoMapper.insertBatch(listBean);
    }

    /**
     * 批量新增或者修改
     */
    @Override
    public Integer addOrUpdateBatch(List<UserInfo> listBean) {
        if (listBean == null || listBean.isEmpty()) {
            return 0;
        }
        return this.userInfoMapper.insertOrUpdateBatch(listBean);
    }

    /**
     * 多条件更新
     */
    @Override
    public Integer updateByParam(UserInfo bean, UserInfoQuery param) {
        StringTools.checkParam(param);
        return this.userInfoMapper.updateByParam(bean, param);
    }

    /**
     * 多条件删除
     */
    @Override
    public Integer deleteByParam(UserInfoQuery param) {
        StringTools.checkParam(param);
        return this.userInfoMapper.deleteByParam(param);
    }

    /**
     * 根据UserId获取对象
     */
    @Override
    public UserInfo getUserInfoByUserId(String userId) {
        return this.userInfoMapper.selectByUserId(userId);
    }

    /**
     * 根据UserId修改
     */
    @Override
    public Integer updateUserInfoByUserId(UserInfo bean, String userId) {
        return this.userInfoMapper.updateByUserId(bean, userId);
    }

    /**
     * 根据UserId删除
     */
    @Override
    public Integer deleteUserInfoByUserId(String userId) {
        return this.userInfoMapper.deleteByUserId(userId);
    }

    /**
     * 根据Email获取对象
     */
    @Override
    public UserInfo getUserInfoByEmail(String email) {
        return this.userInfoMapper.selectByEmail(email);
    }

    /**
     * 根据Email修改
     */
    @Override
    public Integer updateUserInfoByEmail(UserInfo bean, String email) {
        return this.userInfoMapper.updateByEmail(bean, email);
    }

    /**
     * 根据Email删除
     */
    @Override
    public Integer deleteUserInfoByEmail(String email) {
        return this.userInfoMapper.deleteByEmail(email);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void register(String checkCodeKey, String email, String password, String nickName, String checkCode) {
        // 1. 检测验证码
        try {
            String code = (String) redisUtils.get(RedisConstants.REDIS_KEY_CHECK_CODE + checkCodeKey);
            if (code == null || !code.equals(checkCode)) {
                throw new BusinessException("验证码错误");
            }

            // 2. 验证合理性
            UserInfo userInfo = userInfoMapper.selectByEmail(email);
            if (userInfo != null) {
                throw new BusinessException("邮箱已存在");
            }

            // 3. 靓号功能
            String userId;
            boolean isBeauty = false;
            UserInfoBeauty userInfoBeauty = userInfoBeautyMapper.selectByEmail(email);
            if (userInfoBeauty != null && BeautyAccountStatusEnum.NO_USED.getStatus().equals(userInfoBeauty.getStatus())) {
                // 如果存在，并且未被使用
                isBeauty = true;
                userId = UserContactTypeEnum.USER.getPrefix() + userInfoBeauty.getUserId();
            } else {
                userId = StringTools.getRegisterUserId();
            }

            // 4. 注册
            password = StringTools.encodeMd5(password);
            Date curDate = new Date();
            UserInfo registerUser = new UserInfo();
            registerUser
                    .setUserId(userId)
                    .setEmail(email)
                    .setPassword(password)
                    .setNickName(nickName)
                    .setJoinType(JoinTypeEnum.JOIN.getType())
                    .setCreateTime(curDate)
                    .setStatus(UserStatusEnum.ENABLE.getStatus())
                    .setIsDelete(UserDeletedEnum.NOT_DELETED.getStatus())
                    .setLastOffTime(curDate);
            Integer res = userInfoMapper.insert(registerUser);
            if (res < 1) {
                throw new BusinessException(ResponseCodeEnum.CODE_500);
            }
            if (isBeauty) {
                userInfoBeauty.setStatus(BeautyAccountStatusEnum.USED.getStatus());
                userInfoBeautyMapper.updateById(userInfoBeauty, userInfoBeauty.getId());
            }

            // 创建机器人好友
            userContactService.addContact4Robot(userId);
        } finally {
            // 每试一次就删掉，避免用户恶意反复尝试
            redisUtils.delete(RedisConstants.REDIS_KEY_CHECK_CODE + checkCodeKey);
        }
    }

    @Override
    public UserInfoVO login(String checkCodeKey, String email, String password, String checkCode, String ip) {
        try {
            // 1. 合法性验证
            String code = (String) redisUtils.get(RedisConstants.REDIS_KEY_CHECK_CODE + checkCodeKey);
            if (code == null || !code.equals(checkCode)) {
                throw new BusinessException("验证码错误");
            }

            // 2. password = StringTools.encodeMd5(password);	// 前端加密，不要在这里加密
            UserInfo userInfo = userInfoMapper.selectByEmail(email);
            if (userInfo == null || !userInfo.getPassword().equals(password)) {
                throw new BusinessException("账号或密码错误");
            }

            // 3. 是否被封禁
            if (!userInfo.getStatus().equals(UserStatusEnum.ENABLE.getStatus())) {
                throw new BusinessException("账号已禁用");
            }

            // 4. 验证心跳（重复登录）
            Long userHeartBeat = redisComponent.getUserHeartBeat(userInfo.getUserId());
            if (userHeartBeat != null) {
                throw new BusinessException("该账号已在别处登录");
            }
            // TODO 验证时间合理性、地理位置信息

            // TODO 查询群组和联系人
            UserContactQuery query = new UserContactQuery();
            query.setUserId(userInfo.getUserId());
            query.setStatus(UserContactStatusEnum.FRIEND.getStatus());
            List<UserContact> contactList = this.userContactMapper.selectList(query);
            List<String> contactIds = contactList.stream().map(UserContact::getContactId).collect(Collectors.toList());
            redisComponent.cleanUserContact(userInfo.getUserId());
            if (!contactIds.isEmpty()) {
                redisComponent.saveUserContactBatch(userInfo.getUserId(), contactIds);
            }

            // 5. 更新用户信息
            userInfo.setCurrentLoginIp(ip);
            TokenUserInfoDTO tokenUserInfoDTO = updateAndGetDTO(userInfo);

            // 6. 保存token到Redis
            redisComponent.saveTokenUserInfoDTO(tokenUserInfoDTO);

            return CopyUtils.copy(tokenUserInfoDTO, UserInfoVO.class);
        } finally {
            redisUtils.delete(RedisConstants.REDIS_KEY_CHECK_CODE + checkCodeKey);
        }
    }

    private TokenUserInfoDTO updateAndGetDTO(UserInfo userInfo) {
        updateUserInfo(userInfo);
        return getUserInfoDTO(userInfo);
    }

    private void updateUserInfo(UserInfo userInfo) {
        // 更新用户地址
        // TODO 地区码暂不理
        GeoInfoDTO info = IPAreaUtils.getLocationInfo(userInfo.getCurrentLoginIp());
        userInfo.setLastLoginTime(userInfo.getCurrentLoginTime())
                .setLastLoginIp(userInfo.getCurrentLoginIp())
                .setLastLoginArea(userInfo.getCurrentLoginArea())
                .setLastLoginAreaCode(userInfo.getCurrentLoginAreaCode())
                .setCurrentLoginTime(new Date())
                .setCurrentLoginIp(info.getIp())
                .setCurrentLoginArea(info.getCountry() + "-" + info.getProvince() + "-" + info.getAddress());
//                .setCurrentLoginAreaCode(info.getPostalCode());
        userInfoMapper.updateByUserId(userInfo, userInfo.getUserId());
    }

    private TokenUserInfoDTO getUserInfoDTO(UserInfo userInfo) {
        TokenUserInfoDTO tokenUserInfoDTO = new TokenUserInfoDTO();
        tokenUserInfoDTO.setUserId(userInfo.getUserId())
                .setEmail(userInfo.getEmail())
                .setNickName(userInfo.getNickName())
                .setSex(userInfo.getSex())
                .setJoinType(userInfo.getJoinType())
                .setIntroduction(userInfo.getIntroduction())
                .setCurrentLoginTime(userInfo.getCurrentLoginTime())
				.setCurrentLoginIp(userInfo.getCurrentLoginIp())
				.setCurrentLoginArea(userInfo.getCurrentLoginArea());
//				.setCurrentLoginAreaCode(userInfo.getCurrentLoginAreaCode());
        // 判断是不是超管
        String adminEmail = appConfig.getAdminEmail();
        tokenUserInfoDTO.setAdmin(!StringTools.isEmpty(adminEmail) && ArrayUtils.contains(adminEmail.split(","), tokenUserInfoDTO.getEmail()));

        // 设置用户token
        String token = StringTools.encodeMd5(tokenUserInfoDTO.getUserId() + StringTools.getRandomString(UserInfoConstants.USER_ID_LENGTH_20));
        tokenUserInfoDTO.setToken(token);

        return tokenUserInfoDTO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserInfo(UserInfo userInfo, MultipartFile avatarFile, MultipartFile avatarCover) {
        try {
            UserInfo userInfoPO = this.userInfoMapper.selectByUserId(userInfo.getUserId());
            if (null == userInfoPO) {
                throw new BusinessException(ResponseCodeEnum.CODE_600);
            }

            this.userInfoMapper.updateByUserId(userInfo, userInfo.getUserId());

            if (!userInfoPO.getNickName().equals(userInfo.getNickName())) {
                // 更新会话信息中的昵称
                ChatSessionUser chatSessionUser = new ChatSessionUser();
                chatSessionUser.setContactName(userInfo.getNickName());

                ChatSessionUserQuery query = new ChatSessionUserQuery();
                query.setContactId(userInfoPO.getUserId());
                this.chatSessionUserMapper.updateByParam(chatSessionUser, query);

                MessageSendDTO<Object> messageSendDTO = new MessageSendDTO<>();
                messageSendDTO.setContactType(UserContactTypeEnum.USER.getType());
                messageSendDTO.setExtendData(chatSessionUser);
                messageSendDTO.setMessageType(MessageTypeEnum.CONTACT_NAME_UPDATE.getType());
                messageSendDTO.setSendUserId(userInfoPO.getUserId());
                messageSendDTO.setSendUserNickname(userInfoPO.getNickName());

                UserContactQuery query1 = new UserContactQuery();
                query1.setUserId(userInfoPO.getUserId());
                query1.setContactType(UserContactTypeEnum.USER.getType());
                query1.setStatus(UserContactStatusEnum.FRIEND.getStatus());
                List<UserContact> contactList = this.userContactService.findListByParam(query1);

                for (UserContact contact : contactList) {
                    messageSendDTO.setContactId(contact.getContactId());
                    messageHandler.sendMessage(messageSendDTO);
                }

                // 更新Redis中的昵称
                TokenUserInfoDTO userInfoDTO = redisComponent.getUserInfoByUserId(userInfoPO.getUserId());
                userInfoDTO.setNickName(userInfo.getNickName());
                redisComponent.saveTokenUserInfoDTO(userInfoDTO);
            }

            if (null != avatarFile) {
                String path = userInfo.getUserId() + SysConstants.IMAGE_SUFFIX;
                handleAvatarFile(avatarFile, path);
            }
            if (null != avatarCover) {
                String path = userInfo.getUserId() + SysConstants.IMAGE_COVER_SUFFIX;
                handleAvatarFile(avatarCover, path);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
    }

    private void handleAvatarFile(MultipartFile file, String path) {
        try {
            String baseFolder = appConfig.getProjectFolder() + SysConstants.FILE_FOLDER_PATH_FILE + SysConstants.FILE_FOLDER_PATH_AVATAR;
            File targetFileFolder = new File(baseFolder);
            if (!targetFileFolder.exists()) {
                if (!targetFileFolder.mkdirs()) {
                    throw new BusinessException(ResponseCodeEnum.CODE_500);
                }
            }
            String filePath = targetFileFolder.getPath() + File.separator + path;
            file.transferTo(new File(filePath));
        } catch (Exception e) {
            throw new BusinessException(ResponseCodeEnum.CODE_500);
        }
    }

    @Override
    public void updateUserStatusByUserId(String userId, Integer status) {
        UserStatusEnum statusEnum = UserStatusEnum.getByStatus(status);
        if (null == statusEnum) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        UserInfo userInfo = new UserInfo();
        userInfo.setStatus(statusEnum.getStatus());
        this.userInfoMapper.updateByUserId(userInfo, userId);
    }

    @Override
    public void forceOffline(String userId) {
        // 强制下线
        MessageSendDTO messageSendDTO = new MessageSendDTO();
        messageSendDTO.setContactType(UserContactTypeEnum.USER.getType());
        messageSendDTO.setMessageType(MessageTypeEnum.FORCE_OFF_LINE.getType());
        messageSendDTO.setContactId(userId);
        messageHandler.sendMessage(messageSendDTO);
    }
}